package cn.gx.kevin.common.utils;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;

import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class ZipUtils {
    public final static Logger logger = org.slf4j.LoggerFactory.getLogger(ZipUtils.class);
    /**
     * 压缩文件（文件夹）
     *
     * @param path        目标文件流
     * @param format      zip 格式 | rar 格式
     * @param excludePath 是否排除 path目录
     * @throws Exception
     */
    public static String zipFile(File path, String format, boolean excludePath) throws Exception {
        String generatePath = "";
        if (path.isDirectory()) {
            generatePath = path.getParent().endsWith("/") == false
                    ? path.getParent() + File.separator + path.getName() + "." + format
                    : path.getParent() + path.getName() + "." + format;
        } else {
            generatePath = path.getParent().endsWith("/") == false ? path.getParent() + File.separator
                    : path.getParent();
            generatePath += path.getName().substring(0, path.getName().lastIndexOf(".")) + "." + format;
        }
        FileOutputStream outputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        ZipOutputStream out = null;
        try {
            // 输出流
            outputStream = new FileOutputStream(generatePath);
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            // 压缩输出流
            out = new ZipOutputStream(bufferedOutputStream);
            if (excludePath) {
                File[] files = path.listFiles();
                for (File file : files) {
                    zip(out, file, "");
                }
            } else {
                zip(out, path, "");
            }
            out.flush();
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                out.close();
            } catch (Exception e2) {
                // TODO: handle exception
            }
            try {
                outputStream.close();
            } catch (Exception e2) {
                // TODO: handle exception
            }
            try {
                bufferedOutputStream.close();
            } catch (Exception e2) {
                // TODO: handle exception
            }
        }
        return generatePath;
    }

    /**
     * 递归压缩文件
     *
     * @param output    ZipOutputStream 对象流
     * @param file      压缩的目标文件流
     * @param childPath 条目目录
     */
    private static void zip(ZipOutputStream output, File file, String childPath) {
        FileInputStream input = null;
        try {
            // 文件为目录
            if (file.isDirectory()) {
                // 得到当前目录里面的文件列表
                File list[] = file.listFiles();
                if (list.length == 0) {
                    ZipEntry entry = new ZipEntry(childPath + File.separator + file.getName() + File.separator);
                    //entry.setUnixMode(755);//解决文件夹名称linux乱码
                    output.putNextEntry(entry);//压缩文件夹
                } else {
                    childPath = childPath + (childPath.length() == 0 ? "" : File.separator) + file.getName();
                    // 循环递归压缩每个文件
                    for (File f : list) {
                        zip(output, f, childPath);
                    }
                }
            } else {
                // 压缩文件
                childPath = (childPath.length() == 0 ? "" : childPath + File.separator)
                        + file.getName();
                ZipEntry entry = new ZipEntry(childPath);
                // entry.setUnixMode(644);//解决文件名linux乱码
                output.putNextEntry(entry);
                input = new FileInputStream(file);
                int readLen = 0;
                byte[] buffer = new byte[1024 * 8];
                while ((readLen = input.read(buffer, 0, 1024 * 8)) != -1) {
                    output.write(buffer, 0, readLen);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            // 关闭流
            if (input != null) {
                try {
                    input.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }

    }

    /**
     * 对.zip文件进行解压缩
     *
     * @param zipFile
     *            解压缩文件
     * @param destDir
     *            压缩的目标地址，如：D:\\测试 或 /mnt/d/测试
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static List<File> decompressZip(File zipFile, String destDir) {
        byte[] _byte = new byte[1024];
        List<File> _list = new ArrayList<File>();
        ZipFile _zipFile = null;
        try {
            _zipFile = new ZipFile(zipFile);
            for (Enumeration entries = _zipFile.entries(); entries.hasMoreElements();) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                String zipFileName = entry.getName();
                File _file = new File(destDir + File.separator + zipFileName);
                if (entry.isDirectory()) {
                    _file.mkdirs();
                } else {
                    File _parent = _file.getParentFile();
                    if (!_parent.exists()) {
                        _parent.mkdirs();
                    }
                    InputStream _in = null;
                    OutputStream _out = null;
                    try {
                        _in = _zipFile.getInputStream(entry);
                        _out = new FileOutputStream(_file);
                        int len = 0;
                        while ((len = _in.read(_byte)) > 0) {
                            _out.write(_byte, 0, len);
                        }
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    } finally {
                        if (_in != null) {
                            try {
                                _in.close();
                            } catch (Exception x) {

                            }
                        }
                        if (_out != null) {
                            try {
                                _out.flush();
                            } catch (Exception x) {
                            }
                            try {
                                _out.close();
                            } catch (Exception x) {
                            }
                        }
                    }
                    _list.add(_file);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (_zipFile != null) {
                try {
                    _zipFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return _list;
    }



    public static boolean unZip(File zipFile, String descDir) throws Exception {
        boolean flag = false;
        File pathFile = new File(descDir);
        if (!pathFile.exists()) {
            pathFile.mkdirs();
        }
        ZipFile zip = null;
        InputStream in = null;
        OutputStream out = null;
        try {
            // 指定编码，否则压缩包里面不能有中文目录
            zip = new ZipFile(zipFile);
            for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                String zipEntryName = entry.getName();
                in = zip.getInputStream(entry);
                String outPath = (descDir + zipEntryName).replace("/", File.separator).replace("\\", File.separator);
                // 判断路径是否存在,不存在则创建文件路径
                File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator)));
                if (!file.exists()) {
                    file.mkdirs();
                }
                // 判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
                if (new File(outPath).isDirectory()) {
                    continue;
                }
                out = new FileOutputStream(outPath);
                byte[] buf1 = new byte[2048];
                int len;
                while ((len = in.read(buf1)) > 0) {
                    out.write(buf1, 0, len);
                }
                in.close();
                out.close();
                in = null;
                out = null;
            }
            flag = true;

        } catch (Exception e) {
            throw e;
        } finally {
            if (zip != null) {
                try {
                    zip.close();
                } catch (Exception e) {
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                }
            }
        }
        return flag;
    }

    /**
     * 文件压缩
     *
     * @param srcFile 目录或者单个文件
     * @param zipFile 压缩后的ZIP文件
     * @param rootdDirectory 目录名称(路径中最后一个文件夹名称)
     */
    public static void doCompress(String srcFile, String zipFile, String rootdDirectory) throws Exception {
        doCompress(new File(srcFile), new File(zipFile), rootdDirectory);
    }

    /**
     * 文件压缩
     *
     * @param srcFile 目录或者单个文件
     * @param zipFile 压缩后的ZIP文件
     * @param rootdDirectory 目录名称(路径中最后一个文件夹名称)
     */
    public static void doCompress(File srcFile, File zipFile, String rootdDirectory) throws Exception {
        ZipOutputStream out = null;
        try {
            out = new ZipOutputStream(new FileOutputStream(zipFile));
            doCompress(srcFile, out, rootdDirectory);
        } catch (Exception e) {
            throw e;
        } finally {
            out.close();// 记得关闭资源
        }
    }

    public static void doCompress(File file, ZipOutputStream out, String rootdDirectory) throws IOException {
        doCompress(file, out, "", rootdDirectory);
    }

    public static void doCompress(File inFile, ZipOutputStream out, String dir, String rootdDirectory)
            throws IOException {
        if (inFile.isDirectory()) {
            File[] files = inFile.listFiles();
            if (files != null && files.length > 0) {
                for (File file : files) {
                    String name = inFile.getName();
                    if (!"".equals(dir)) {
                        name = dir + "/" + name;
                    }
                    doCompress(file, out, name, rootdDirectory);
                }
            }
        } else {
            dir = dir.replaceAll(rootdDirectory, "");
            doZip(inFile, out, dir);
        }
    }

    public static void doZip(File inFile, ZipOutputStream out, String dir) throws IOException {
        String entryName = null;
        if (!"".equals(dir)) {
            entryName = dir + "/" + inFile.getName();
        } else {
            entryName = inFile.getName();
        }
        ZipEntry entry = new ZipEntry(entryName);
        out.putNextEntry(entry);

        int len = 0;
        byte[] buffer = new byte[1024];
        FileInputStream fis = new FileInputStream(inFile);
        while ((len = fis.read(buffer)) > 0) {
            out.write(buffer, 0, len);
            out.flush();
        }
        out.closeEntry();
        fis.close();
    }

    /**
     * 条目名称使用的默认字符.
     */
    public static String CHARSET_GBK = "utf-8";

    /**
     * 使用ZIP算法进行压缩.
     * @param sourceFileBytesMap 待压缩文件的Map集合.
     * @return 压缩后的ZIP文件字节数组.
     * @throws Exception 压缩过程中可能发生的异常，若发生异常，则返回的字节数组长度为0.
     */
    public static byte[] compressByZip(Map<String, byte[]> sourceFileBytesMap) throws Exception {
        // 变量定义.
        ZipEntry zipEntry = null;
        ZipOutputStream zipZos = null;
        ByteArrayOutputStream zipBaos = null;

        try {
            // 压缩文件变量初始化.
            zipBaos = new ByteArrayOutputStream();
            zipZos = new ZipOutputStream(zipBaos);
            // 将文件添加到ZIP条目中.
            if (null != sourceFileBytesMap && sourceFileBytesMap.size() > 0) {
                for (Map.Entry<String, byte[]> singleFile : sourceFileBytesMap.entrySet()) {
                    zipEntry = new ZipEntry(singleFile.getKey());
                    zipZos.putNextEntry(zipEntry);
                    zipZos.write(singleFile.getValue());
                }
            } else {
                zipBaos = new ByteArrayOutputStream();
            }
        } finally {
            if (null != zipBaos)
                zipBaos.close();
            if (null != zipZos)
                zipZos.close();
        }
        return zipBaos.toByteArray();
    }

    /**
     * 使用ZIP算法进行压缩.
     * @param sourceFileBytesMap 待压缩文件的Map集合.
     * @return 压缩后的ZIP文件字节数组.
     * @throws Exception 压缩过程中可能发生的异常，若发生异常，则返回的字节数组长度为0.
     */
    public static byte[] compressByZipJdkLower7(Map<String, byte[]> sourceFileBytesMap) throws Exception {
        return compressByZipJdkLower7(sourceFileBytesMap, CHARSET_GBK);
    }

    /**
     * 使用ZIP算法进行压缩.
     * @param sourceFileBytesMap 待压缩文件的Map集合.
     * @return 压缩后的ZIP文件字节数组.
     * @throws Exception 压缩过程中可能发生的异常，若发生异常，则返回的字节数组长度为0.
     */
    public static byte[] compressByZipJdkLower7(Map<String, byte[]> sourceFileBytesMap, String charset)
            throws Exception {
        // 变量定义.
        ByteArrayOutputStream zipBaos = null;
        ZipOutputStream zipZos = null;

        try {
            // 压缩文件变量初始化.
            zipBaos = new ByteArrayOutputStream();
            zipZos = new ZipOutputStream(zipBaos);
            // 将文件添加到ZIP条目中.
            if (null != sourceFileBytesMap && sourceFileBytesMap.size() > 0) {
                for (Map.Entry<String, byte[]> singleFile : sourceFileBytesMap.entrySet()) {
                    zipZos.putNextEntry(new ZipEntry((singleFile.getKey())));
                    zipZos.write(singleFile.getValue());
                }
            } else {
                zipBaos = new ByteArrayOutputStream();
            }
        } finally {
            if (null != zipBaos)
                zipBaos.close();
            if (null != zipZos)
                zipZos.close();
        }
        return zipBaos.toByteArray();
    }

    /**
     * 使用ZIP算法进行解压.
     * @param sourceZipFileBytes ZIP文件字节数组.
     * @return 解压后的文件Map集合.
     * @throws Exception 解压过程中可能发生的异常，若发生异常，返回Map集合长度为0.
     */
    public static Map<String, byte[]> decompressByZip(byte[] sourceZipFileBytes) throws Exception {
        // 变量定义.
        String zipEntryName = null;
        ZipEntry singleZipEntry = null;
        ZipInputStream sourceZipZis = null;
        BufferedInputStream sourceZipBis = null;
        ByteArrayInputStream sourceZipBais = null;
        Map<String, byte[]> targetFilesFolderMap = null;

        try {
            // 解压变量初始化.
            targetFilesFolderMap = new HashMap<String, byte[]>();
            sourceZipBais = new ByteArrayInputStream(sourceZipFileBytes);
            sourceZipBis = new BufferedInputStream(sourceZipBais);
            sourceZipZis = new ZipInputStream(sourceZipBis);
            /* 条目解压缩至Map中. */
            while ((singleZipEntry = sourceZipZis.getNextEntry()) != null) {
                zipEntryName = singleZipEntry.getName();
                targetFilesFolderMap.put(zipEntryName, IOUtils.toByteArray(sourceZipZis));
            }
        } finally {
            if (null != sourceZipZis)
                sourceZipZis.close();
            if (null != sourceZipBis)
                sourceZipBis.close();
            if (null != sourceZipBais)
                sourceZipBais.close();

        }
        return targetFilesFolderMap;
    }


    /**
     * Mapped File way MappedByteBuffer 可以在处理大文件时，提升性能
     *
     * @param filename
     * @return
     * @throws IOException
     */
    public static byte[] readFile2ByteArray(String filename) throws IOException {
        FileChannel fc = null;
        try {
            fc = new RandomAccessFile(filename, "r").getChannel();
            MappedByteBuffer byteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0,fc.size()).load();
            byte[] result = new byte[(int) fc.size()];
            if (byteBuffer.remaining() > 0) {
                byteBuffer.get(result, 0, byteBuffer.remaining());
            }
            return result;
        } catch (IOException e) {
            throw e;
        } finally {
            if(fc != null){
                try {
                    fc.close();
                } catch (IOException e) {
                }
            }
        }
    }
}
